import gtk
from rect import Rect

pixbuf_cache = {}

class BaseBackground:
  def __init__(self):
    self.cache = None
    self.cache_rect = Rect((0, 0, 0, 0))
    self.cacheable = True

  def GetMinHeight(self):
    return 0

  def GetMinWidth(self):
    return 0

  def Draw(self, window, rect, clip_rect):
    assert window is not None
    if self.cacheable:
      if self.cache:
        if (self.cache_rect.width != rect.width or
            self.cache_rect.height != rect.height):
          self.cache = None

      if self.cache is None:
        self.cache = gtk.gdk.Pixmap(
            window, rect.width, rect.height, -1)
        self.cache_rect = Rect((0, 0, rect.width, rect.height))
        self.DrawToBuffer(
            self.cache, window, Rect((0, 0, rect.width, rect.height)), None)
      self.CopyCacheToWindow(self.cache, window, rect, clip_rect)
    else:
      self.DrawToBuffer(window, window, rect, clip_rect)

  def DrawToBuffer(self, cache, window, rect, clip_rect=None):
    pass

  def CopyCacheToWindow(self, cache, window, rect, clip_rect):
    gc = gtk.gdk.GC(window)
    if clip_rect:
      gc.set_clip_rectangle(clip_rect.GetGdkRectangle())
    window.draw_drawable(
        gc,
        cache,
        0,
        0,
        rect.left,
        rect.top,
        rect.width,
        rect.height)

class SolidBackground(BaseBackground):
  def __init__(self, color):
    BaseBackground.__init__(self)
    self.color = color
    self._background_cmap_color = None

  def DrawToBuffer(self, cache, window, rect, clip_rect=None):
    if not self._background_cmap_color:
      cmap = window.get_colormap()
      self._background_cmap_color = cmap.alloc_color(self.color)
    colorgc = gtk.gdk.GC(window)
    colorgc.set_foreground(self._background_cmap_color)
    colorgc.set_fill(gtk.gdk.SOLID)

    # Draw the background color
    if clip_rect:
      colorgc.set_clip_rectangle(clip_rect.GetGdkRectangle())
    cache.draw_rectangle(
        colorgc, True, rect.left, rect.top, rect.width, rect.height)

class VerticalGradientBackground(BaseBackground):
  def __init__(self, top_color, bottom_color):
    BaseBackground.__init__(self)
    self.top_color = self.SplitColor(top_color)
    self.bottom_color = self.SplitColor(bottom_color)
    self.dred = self.bottom_color[0] - self.top_color[0]
    self.dgreen = self.bottom_color[1] - self.top_color[1]
    self.dblue = self.bottom_color[2] - self.top_color[2]

  def SplitColor(self, color):
    return int(color[1:3], 16), int(color[3:5], 16), int(color[5:7], 16)

  def DrawToBuffer(self, cache, window, rect, clip_rect=None):
    cmap = window.get_colormap()
    colorgc = gtk.gdk.GC(window)
    colorgc.set_fill(gtk.gdk.SOLID)
    if clip_rect:
      colorgc.set_clip_rectangle(clip_rect.GetGdkRectangle())

    for i in range(rect.height):
      colorgc.set_foreground(
          cmap.alloc_color(self.GradientColor(i, rect.height - 1)))
      cache.draw_line(colorgc, 0, i, rect.right, i)

  def GradientColor(self, i, n):
    fraction = float(i) / float(n)
    red = self.top_color[0] + self.dred * fraction
    green = self.top_color[1] + self.dgreen * fraction
    blue = self.top_color[2] + self.dblue * fraction
    return '#%02x%02x%02x' % (red, green, blue)

class VerticalGradientWithLowerBorderBackground(VerticalGradientBackground):
  def __init__(self, top_color, bottom_color):
    VerticalGradientBackground.__init__(self, top_color, bottom_color)

  def DrawToBuffer(self, cache, window, rect, clip_rect=None):
    VerticalGradientBackground.DrawToBuffer(self, cache, window, rect,
        clip_rect)
    cmap = window.get_colormap()
    colorgc = gtk.gdk.GC(window)
    colorgc.set_fill(gtk.gdk.SOLID)
    if clip_rect:
      colorgc.set_clip_rectangle(clip_rect.GetGdkRectangle())
    colorgc.set_foreground(cmap.alloc_color('#000000'))
    cache.draw_line(colorgc, 0, rect.height - 1, rect.right, rect.height - 1)

class ShapeBackground(BaseBackground):
  def __init__(self, left_image, mid_image, right_image):
    BaseBackground.__init__(self)
    self.left_image = LoadCachedPixbuf(left_image)
    self.mid_image = LoadCachedPixbuf(mid_image)
    self.right_image = LoadCachedPixbuf(right_image)
    # Don't draw to cache because of transparency.
    self.cacheable = False
    assert self.left_image
    assert self.mid_image
    assert self.right_image

  def GetMinHeight(self):
    return max(self.left_image.get_height(),
        self.mid_image.get_height(),
        self.right_image.get_height())

  def GetMinWidth(self):
    return (self.left_image.get_width() +
        self.mid_image.get_width() +
        self.right_image.get_width())

  def DrawToBuffer(self, cache, window, rect, clip_rect=None):
    offset = 0
    offset += self.DrawImageToCache(self.left_image, cache, window,
        offset, rect, clip_rect)
    right_width = self.right_image.get_width()
    while offset < rect.width - right_width:
      offset += self.DrawImageToCache(self.mid_image, cache, window,
          offset, rect, clip_rect)
    self.DrawImageToCache(self.right_image, cache, window, offset, rect,
        clip_rect)

  def DrawImageToCache(self, image, cache, window, offset, rect, clip_rect):
    w, h = image.get_width(), image.get_height()
    gc = gtk.gdk.GC(window)
    if clip_rect:
      gc.set_clip_rectangle(clip_rect.GetGdkRectangle())
    cache.draw_pixbuf(gc, image, 0, 0, rect.left + offset, rect.top, w, h)
    return w

class Image(BaseBackground):
  def __init__(self, image):
    BaseBackground.__init__(self)
    self.image = LoadCachedPixbuf(image)
    # Don't draw to cache because of transparency.
    self.cacheable = False
    assert self.image

  def GetMinHeight(self):
    return self.image.get_height()

  def GetMinWidth(self):
    return self.image.get_width()

  def DrawToBuffer(self, cache, window, rect, clip_rect=None):
    w, h = self.image.get_width(), self.image.get_height()
    gc = gtk.gdk.GC(window)
    if clip_rect:
      gc.set_clip_rectangle(clip_rect.GetGdkRectangle())
    cache.draw_pixbuf(gc, self.image, 0, 0, rect.left, rect.top, w, h)

def LoadCachedPixbuf(filename):
  global pixbuf_cache

  if filename in pixbuf_cache:
    return pixbuf_cache[filename]

  image = gtk.gdk.pixbuf_new_from_file(filename)
  pixbuf_cache[filename] = image
  assert image is not None
  return image

class RectBackground(BaseBackground):
  def __init__(self, center_color, prefix, suffix):
    BaseBackground.__init__(self)
    self.images = {}
    self.center_color = center_color
    self._background_cmap_color = None
    for h in ("l", "", "r"):
      for v in ("t", "", "b"):
        if h or v:
          self.images[(h, v)] = LoadCachedPixbuf(prefix + v + h + suffix)
          assert self.images[(h, v)]
    # Don't draw to cache because of transparency.
    self.cacheable = False

  def GetMinHeight(self):
    min_height = 0
    for h in ("l", "", "r"):
      height = 0
      for v in ("t", "", "b"):
        if h or v:
          height += self.images[(h, v)].get_height()
      min_height = max(min_height, height)
    return min_height

  def GetMinWidth(self):
    min_width = 0
    width = 0
    for v in ("t", "", "b"):
      for h in ("l", "", "r"):
        if h or v:
          width += self.images[(h, v)].get_width()
      min_width = max(min_width, width)
    return min_width

  def DrawToBuffer(self, cache, window, rect, clip_rect=None):
    # Draw corners
    left_t, top_l = self.DrawImageToCache(
        self.images[("l", "t")],
        cache,
        window,
        0,
        0,
        rect,
        clip_rect)

    left_b, bottom_l = self.DrawImageToCache(
        self.images[("l", "b")],
        cache,
        window,
        0,
        None,
        rect,
        clip_rect)

    right_t, bottom_r = self.DrawImageToCache(
        self.images[("r", "b")],
        cache,
        window,
        None,
        None,
        rect,
        clip_rect)

    right_b, top_r = self.DrawImageToCache(
        self.images[("r", "t")],
        cache,
        window,
        None,
        0,
        rect,
        clip_rect)

    # Draw the center
    if not self._background_cmap_color:
      cmap = window.get_colormap()
      self._background_cmap_color = cmap.alloc_color(self.center_color)
    colorgc = gtk.gdk.GC(window)
    colorgc.set_foreground(self._background_cmap_color)
    colorgc.set_fill(gtk.gdk.SOLID)

    if clip_rect:
      colorgc.set_clip_rectangle(clip_rect.GetGdkRectangle())
    x1 = max(left_t, left_b)
    y1 = max(top_l, top_r)
    x2 = min(right_t, right_b)
    y2 = min(bottom_l, bottom_r)

    cache.draw_rectangle(
        colorgc, True, x1, y1, x2-x1, y2-y1)

    # Draw edges
    for x in range(left_t, right_t):
      self.DrawImageToCache(
          self.images[("", "t")],
          cache,
          window,
          x,
          0,
          rect,
          clip_rect)

    for x in range(left_b, right_b):
      self.DrawImageToCache(
          self.images[("", "b")],
          cache,
          window,
          x,
          None,
          rect,
          clip_rect)

    for y in range(top_l, bottom_l):
      self.DrawImageToCache(
          self.images[("l", "")],
          cache,
          window,
          0,
          y,
          rect,
          clip_rect)

    for y in range(top_r, bottom_r):
      self.DrawImageToCache(
          self.images[("r", "")],
          cache,
          window,
          None,
          y,
          rect,
          clip_rect)

  def DrawImageToCache(self, image, cache, window, x_offset, y_offset, rect,
      clip_rect):
    w, h = image.get_width(), image.get_height()

    if x_offset is None:
      x_offset = rect.width - w
      rw = x_offset
    else:
      rw = w
    if y_offset is None:
      y_offset = rect.height - h
      rh = y_offset
    else:
      rh = h

    gc = gtk.gdk.GC(window)
    if clip_rect:
      gc.set_clip_rectangle(clip_rect.GetGdkRectangle())
    cache.draw_pixbuf(gc, image, 0, 0, rect.left + x_offset,
        rect.top + y_offset, w, h)
    return rw, rh

